package com.jxpanda.commons.toolkit.json;

import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.JsonToken;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.BeanDescription;
import com.fasterxml.jackson.databind.DeserializationConfig;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.JsonDeserializer;
import com.fasterxml.jackson.databind.deser.BeanDeserializerModifier;
import com.fasterxml.jackson.databind.module.SimpleModule;
import com.fasterxml.jackson.databind.type.CollectionType;
import com.fasterxml.jackson.databind.type.MapType;
import com.jxpanda.commons.constant.DefaultValueConstant;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;

import java.lang.reflect.Type;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
import java.util.stream.Stream;

/**
 * null 值的【反序列化】处理
 * 与NullSerializer不同的是，这个类使用Module的实现来加载
 *
 * @author Panda
 */
@Slf4j
public class DefaultValueModule extends SimpleModule {

    private static final String LOG_MARKER = "[NULL DESERIALIZER]";

    private static final Set<Class<?>> COLLECTION_AND_MAP = Stream.of(List.class, Set.class, Map.class).collect(Collectors.toSet());

    private static final Set<Class<?>> BASIC_TYPE_LIST = DefaultValueConstant.VALUE_MAP.keySet()
            .stream().filter(clazz -> !COLLECTION_AND_MAP.contains(clazz))
            .collect(Collectors.toSet());

    public DefaultValueModule() {
        setDeserializerModifier(new Modifier());
    }

    private static class Modifier extends BeanDeserializerModifier {

        @Override
        public JsonDeserializer<?> modifyDeserializer(DeserializationConfig config, BeanDescription beanDesc, JsonDeserializer<?> deserializer) {
            if (BASIC_TYPE_LIST.contains(beanDesc.getBeanClass())) {
                return new JsonDeserializer<>() {
                    @Override
                    @SneakyThrows
                    public Object deserialize(JsonParser parser, DeserializationContext context) {
                        return deserializer.deserialize(parser, context);
                    }

                    @Override
                    public Object getNullValue(DeserializationContext context) {
                        return DefaultValueConstant.VALUE_MAP.getOrDefault(beanDesc.getBeanClass(), null);
                    }
                };
            }
            return deserializer;
        }

        @Override
        public JsonDeserializer<Collection<?>> modifyCollectionDeserializer(DeserializationConfig config, CollectionType type, BeanDescription beanDesc, JsonDeserializer<?> deserializer) {
            return new JsonDeserializer<>() {
                @Override
                @SneakyThrows
                public Collection<?> deserialize(final JsonParser parser, final DeserializationContext context) {
                    Collection<?> result = emptyInstance(beanDesc);
                    parser.setCurrentValue(result);
                    while ((parser.nextToken()) != JsonToken.END_ARRAY) {
                        result.add(parser.readValueAs(new TypeReference<>() {
                            @Override
                            public Type getType() {
                                return type.getContentType();
                            }
                        }));
                    }
                    return result;
                }

                @Override
                public Collection<?> getNullValue(final DeserializationContext context) {
                    return emptyInstance(beanDesc);
                }

            };
        }

        @Override
        public JsonDeserializer<?> modifyMapDeserializer(DeserializationConfig config, MapType type, BeanDescription beanDesc, JsonDeserializer<?> deserializer) {
            return new JsonDeserializer<>() {
                @Override
                @SneakyThrows
                public Map<String, Object> deserialize(final JsonParser parser, final DeserializationContext context) {
                    Map<String, Object> result = emptyInstance(beanDesc);
                    String key = null;
                    if (parser.isExpectedStartObjectToken()) {
                        key = parser.nextFieldName();
                    } else {
                        JsonToken token = parser.currentToken();
                        if (token != JsonToken.FIELD_NAME) {
                            context.reportWrongTokenException(this, JsonToken.FIELD_NAME, null);
                        }
                        if (token != JsonToken.END_OBJECT) {
                            key = parser.currentName();
                        }
                    }

                    for (; key != null; key = parser.nextFieldName()) {
                        parser.nextToken();
                        Object value = parser.readValueAs(new TypeReference<>() {
                            @Override
                            public Type getType() {
                                return type.getContentType();
                            }
                        });
                        if (value != null) {
                            result.put(key, value);
                        }
                    }
                    return result;
                }

                @Override
                public Map<?, ?> getNullValue(final DeserializationContext context) {
                    return emptyInstance(beanDesc);
                }

            };
        }

        @SuppressWarnings("unchecked")
        private <T> T emptyInstance(BeanDescription beanDescription) {
            T instance = null;
            try {
                instance = (T) beanDescription.getBeanClass().getConstructor().newInstance();
            } catch (Exception e) {
                log.warn(LOG_MARKER + " Create class error:" + beanDescription.getBeanClass());
            }
            return instance;
        }

    }

}
